--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 2e759108c1dd85680a7ec1c9865c5facb1b41799
Parents : e266279
Author : Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-04-13T23:35:23-05:00
fix(electron): improve child process termination handling during app quit to ensure graceful shutdown
Changes
4 files changed, 90 insertions(+), 29 deletions(-)
Diff
diff --git a/electron/main-legacy.js b/electron/main-legacy.js
index af61fe2c..ac55abbf 100644
--- a/electron/main-legacy.js
+++ b/electron/main-legacy.js
@@ -500,21 +500,47 @@ app.whenReady().then(async () => {
});
function quit() {
- // kill python process
- if (exeChildProcess) {
- exeChildProcess.kill("SIGKILL");
+ if (!exeChildProcess) {
+ app.quit();
+ return;
}
-
- // quit electron app
- app.quit();
+ if (exeChildProcess.exitCode !== null || exeChildProcess.signalCode !== null) {
+ return;
+ }
+ try {
+ exeChildProcess.kill("SIGTERM");
+ } catch (e) {
+ log(e);
+ try {
+ exeChildProcess.kill("SIGKILL");
+ } catch (e2) {
+ log(e2);
+ }
+ app.quit();
+ return;
+ }
+ const timeoutMs = 5000;
+ const timeout = setTimeout(() => {
+ try {
+ if (
+ exeChildProcess &&
+ exeChildProcess.exitCode === null &&
+ exeChildProcess.signalCode === null
+ ) {
+ exeChildProcess.kill("SIGKILL");
+ }
+ } catch (e) {
+ log(e);
+ }
+ app.quit();
+ }, timeoutMs);
+ exeChildProcess.once("exit", () => {
+ clearTimeout(timeout);
+ app.quit();
+ });
}
// quit electron if all windows are closed
app.on("window-all-closed", () => {
quit();
});
-
-// make sure child process is killed if app is quiting
-app.on("quit", () => {
- quit();
-});
diff --git a/electron/main.js b/electron/main.js
index 70e21aa1..144b86c5 100644
--- a/electron/main.js
+++ b/electron/main.js
@@ -752,21 +752,47 @@ app.whenReady().then(async () => {
});
function quit() {
- // kill python process
- if (exeChildProcess) {
- exeChildProcess.kill("SIGKILL");
+ if (!exeChildProcess) {
+ app.quit();
+ return;
}
-
- // quit electron app
- app.quit();
+ if (exeChildProcess.exitCode !== null || exeChildProcess.signalCode !== null) {
+ return;
+ }
+ try {
+ exeChildProcess.kill("SIGTERM");
+ } catch (e) {
+ log(e);
+ try {
+ exeChildProcess.kill("SIGKILL");
+ } catch (e2) {
+ log(e2);
+ }
+ app.quit();
+ return;
+ }
+ const timeoutMs = 5000;
+ const timeout = setTimeout(() => {
+ try {
+ if (
+ exeChildProcess &&
+ exeChildProcess.exitCode === null &&
+ exeChildProcess.signalCode === null
+ ) {
+ exeChildProcess.kill("SIGKILL");
+ }
+ } catch (e) {
+ log(e);
+ }
+ app.quit();
+ }, timeoutMs);
+ exeChildProcess.once("exit", () => {
+ clearTimeout(timeout);
+ app.quit();
+ });
}
// quit electron if all windows are closed
app.on("window-all-closed", () => {
quit();
});
-
-// make sure child process is killed if app is quiting
-app.on("quit", () => {
- quit();
-});
diff --git a/meshchatx/meshchat.py b/meshchatx/meshchat.py
index 28985c83..a4299b5e 100644
--- a/meshchatx/meshchat.py
+++ b/meshchatx/meshchat.py
@@ -2296,6 +2296,15 @@ class ReticulumMeshChat:
# web server has shutdown, likely ctrl+c, but if we don't do the following, the script never exits
async def shutdown(self, app):
+ for identity_hash in list(self.contexts.keys()):
+ ctx = self.contexts.get(identity_hash)
+ if ctx is None:
+ continue
+ bh = getattr(ctx, "bot_handler", None)
+ if bh is not None:
+ with contextlib.suppress(Exception):
+ bh.stop_all()
+
if hasattr(self, "page_node_manager"):
self.page_node_manager.teardown()
diff --git a/meshchatx/src/backend/identity_context.py b/meshchatx/src/backend/identity_context.py
index c4dccfa3..861d919e 100644
--- a/meshchatx/src/backend/identity_context.py
+++ b/meshchatx/src/backend/identity_context.py
@@ -475,6 +475,13 @@ class IdentityContext:
self.auto_propagation_manager.stop()
self.auto_propagation_manager = None
+ if self.bot_handler:
+ try:
+ self.bot_handler.stop_all()
+ except Exception as e:
+ print(f"Error while stopping bots for {self.identity_hash}: {e}")
+ self.bot_handler = None
+
# 1. Deregister announce handlers
for handler in self.announce_handlers:
with contextlib.suppress(Exception):
@@ -586,13 +593,6 @@ class IdentityContext:
if self.nomadnet_manager:
self.nomadnet_manager = None
- if self.bot_handler:
- try:
- self.bot_handler.stop_all()
- except Exception as e:
- print(f"Error while stopping bots for {self.identity_hash}: {e}")
- self.bot_handler = None
-
if self.database:
try:
if not getattr(self.app, "emergency", False):
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────